package timbap.visualizer.api.context;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import processing.core.PApplet;

import javax.swing.JOptionPane;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.DatagramSocket;
import java.util.Properties;

/**
 * The <code>Configuration</code> class represents the configuration
 * of the timbap visualizer.
 *
 * @author  Andreas Pabst
 */
public class Configuration {
    
    private static final Log LOG = LogFactory.getLog(Configuration.class);

    public static String DEFAULT_TIMBAP_PROPERTIES_PATH = "conf/timbap.properties";
    public static String DEFAULT_PROJECTION_PROPERTIES_PATH = "conf/projection.properties";

	
    private File projConfigFile;
    private Properties projProperties;
    
    private File defaultArtworkFile;
	private File defaultPlaylistDirectory;
    private boolean useTurntableForPlaylistSelection;
    private boolean reselectPlaylistWhenTurntableSwitchedOff;

    private boolean fullScreenEnabled;
	private int screenWidth;
	private int screenHeight;
    private int screenRotation;

    private int itemCount;
	private float velocityFactor;
    private int modeSwitchTimeInMs;

    private int listeningPort;
	private int sendingPort;

	private int textureCacheSize;
	private int maximumFrameRate;
    private float browsingScaleFactor;
	private float playbackScaleFactor;
	private String standardFont;
	private String debugFont;

	private boolean iTunesParsingEnabled;
	private File iTunesMusicLibraryFile;
	private File iTunesToM3UStylesheetFile;

    private float posCloudScaleFactor = 0.33f;
    private int posCloudXPos = 0;
    private int posCloudYPos = 0;
    private int posCloudRotation = -45;

    public void load() {
		
	    File tMainConfigFile = new File(DEFAULT_TIMBAP_PROPERTIES_PATH);
        try {
            loadMainProperties(tMainConfigFile);
        } catch (IOException e) {
            LOG.error("Could not parse configuration from timbap properties file " + DEFAULT_TIMBAP_PROPERTIES_PATH);
        }

        projConfigFile = new File(DEFAULT_PROJECTION_PROPERTIES_PATH);
        try {
            loadProjectionProperties(projConfigFile);
        } catch (IOException e) {
            LOG.error("Could not parse configuration from projection properties file " + DEFAULT_PROJECTION_PROPERTIES_PATH);
        }
    }

    private void loadMainProperties(File pPropertiesFile) throws IOException {
        InputStream tCfgFileInputStream;
	    if (File.separatorChar == '\\') {
	    	// Need to replace backslashes in properties file
	    	byte[] tCfgFileBytes = PApplet.loadBytes(new FileInputStream(pPropertiesFile));
	    	byte backSlashByte = '\\';
	    	byte forwardSlashByte = '/';
	    	for (int i=0; i < tCfgFileBytes.length; i++) {
	    		if (tCfgFileBytes[i] == backSlashByte) {
	    			if (i == 0 || i == tCfgFileBytes.length - 1 || 
	    				(tCfgFileBytes[i+1] != backSlashByte && tCfgFileBytes[i-1] != backSlashByte)) {
	    				tCfgFileBytes[i] = forwardSlashByte;
	    			}
	    		}
	    	}
	    	tCfgFileInputStream = new ByteArrayInputStream(tCfgFileBytes);
	    } else {
	    	tCfgFileInputStream = new FileInputStream(pPropertiesFile);
	    }
	    
	    LOG.info("Parsing main configuration properties from file " + pPropertiesFile);
	    Properties tMainProperties = new Properties();
		tMainProperties.load(tCfgFileInputStream);

		defaultArtworkFile = loadFileProperty(tMainProperties, "DEFAULT_ARTWORK_FILE", new File("conf/timbap_logo.jpg"));
		defaultPlaylistDirectory = loadFileProperty(tMainProperties, "DEFAULT_PLAYLIST_DIRECTORY", new File("playlists"));
        useTurntableForPlaylistSelection = loadBooleanProperty(tMainProperties, "PLAYLIST_SELECTION_USING_TURNTABLE", false);
        reselectPlaylistWhenTurntableSwitchedOff = loadBooleanProperty(tMainProperties, "RESELECT_PLAYLIST_AFTER_TURNTABLE_SWITCHED_OFF", false);

        standardFont = loadStringProperty(tMainProperties, "STANDARD_FONT","Ziggurat.vlw");
		debugFont = loadStringProperty(tMainProperties, "DEBUG_FONT","CourierNew-12.vlw");

		fullScreenEnabled = loadBooleanProperty(tMainProperties, "FULL_SCREEN_MODE_ENABLED", false);
		screenWidth = loadIntegerProperty(tMainProperties, "SCREEN_WIDTH", 1024);
		screenHeight = loadIntegerProperty(tMainProperties, "SCREEN_HEIGHT", 768);

        itemCount = loadIntegerProperty(tMainProperties, "ITEM_COUNT", 5);
		velocityFactor = loadFloatProperty(tMainProperties, "VELOCITY_FACTOR", 1.0f);
        modeSwitchTimeInMs = loadIntegerProperty(tMainProperties, "MODE_SWITCH_TIME_IN_MS", 2000);
        browsingScaleFactor = loadFloatProperty(tMainProperties, "BROWSING_SCALE_FACTOR", 1);
        playbackScaleFactor = loadFloatProperty(tMainProperties, "PLAYBACK_SCALE_FACTOR", 1);
		listeningPort = loadIntegerProperty(tMainProperties, "LISTENING_PORT", 3001);
		sendingPort = loadIntegerProperty(tMainProperties, "SENDING_PORT", 3002);
		textureCacheSize = loadIntegerProperty(tMainProperties, "TEXTURE_CACHE_SIZE", 50);
		maximumFrameRate = loadIntegerProperty(tMainProperties, "MAXIMUM_FRAME_RATE", 40);
		
		iTunesParsingEnabled = loadBooleanProperty(tMainProperties, "REPARSE_ITUNES_MUSIC_LIBRARY_ON_STARTUP", false);
		iTunesMusicLibraryFile = loadFileProperty(tMainProperties, "ITUNES_MUSIC_LIBRARY_PATH", null);
		iTunesToM3UStylesheetFile = loadFileProperty(tMainProperties, "ITUNES_TO_M3U_STYLESHEET_PATH", new File("conf/iTunesToM3U.xsl"));
			 
	}

    private void loadProjectionProperties(File pPropertiesFile) throws IOException {

        InputStream tCfgFileInputStream = new FileInputStream(pPropertiesFile);

	    LOG.info("Parsing projection properties from file " + pPropertiesFile);
	    projProperties = new Properties();
		projProperties.load(tCfgFileInputStream);

        screenRotation = loadIntegerProperty(projProperties, "SCREEN_ROTATION", 0);
        posCloudRotation = loadIntegerProperty(projProperties, "POSCLOUD_ROTATION", -45);
        posCloudXPos = loadIntegerProperty(projProperties, "POSCLOUD_XPOS", 0);
        posCloudYPos = loadIntegerProperty(projProperties, "POSCLOUD_YPOS", 0);
        posCloudScaleFactor = loadFloatProperty(projProperties, "POSCLOUD_SCALE_FACTOR", 0.33f);
    }

    public void persistProjectionProperties() throws IOException {
        FileOutputStream tOut = new FileOutputStream(projConfigFile);
        projProperties.store(tOut, "Properties for projection adjustment. " +
                "Adjustable at runtime using 'R', 'Z', 'W', 'A', 'S', 'D', '+' and '-' keys");
    }


    public String loadStringProperty(Properties pProperties, String pPropertyName, String pDefaultValue) {
        String tStringValue = pProperties.getProperty(pPropertyName);
        if (tStringValue != null) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Property key '" + pPropertyName + "' set to '" + tStringValue + "'");
            }
        } else {
            tStringValue = pDefaultValue;
            LOG.warn("Property key '" + pPropertyName + "' not found in config file. Using default value.");
        }
        return tStringValue;
    }
	
	public File loadFileProperty(Properties pProperties, String pPropertyName, File pDefaultValue) {
        File tFile = pDefaultValue;
        String tStringValue = pProperties.getProperty(pPropertyName);
        if (tStringValue != null) {
            try {
                tFile = new File(tStringValue);
            } catch (Exception e) {
                LOG.warn("Parsing of property '" + pPropertyName + "' from config file failed. Using default value.");
            }
            if (LOG.isDebugEnabled()) {
                LOG.debug("Property key '" + pPropertyName + "' set to '" + tFile + "'");
            }
        } else {
            LOG.warn("Property key '" + pPropertyName + "' not found in config file. Using default value " + pDefaultValue);
        }
        return tFile;
    }
	
	public int loadIntegerProperty(Properties pProperties, String pPropertyName, int pDefaultValue) {
	    int tIntValue = pDefaultValue;
	    String tStringValue = pProperties.getProperty(pPropertyName);
	    if (tStringValue != null) {
            try {
    	        tIntValue = Integer.parseInt(tStringValue);
    	    } catch (Exception e) {
    	        LOG.warn("Parsing of property '" + pPropertyName + "' from config file failed. Using default value.");
    	    }
    	    if (LOG.isDebugEnabled()) {
    	        LOG.debug("Property key '" + pPropertyName + "' set to '" + tIntValue + "'");
    	    }
        } else {
            LOG.warn("Property key '" + pPropertyName + "' not found in config file. Using default value " + pDefaultValue);
        }
	    return tIntValue;
	}
	
	public float loadFloatProperty(Properties pProperties, String pPropertyName, float pDefaultValue) {
        float tFloatValue = pDefaultValue;
        String tStringValue = pProperties.getProperty(pPropertyName);
        if (tStringValue != null) {
            try {
                tFloatValue = Float.parseFloat(tStringValue);
            } catch (Exception e) {
                LOG.warn("Parsing of property '" + pPropertyName + "' from config file failed. Using default value.");
            }
            if (LOG.isDebugEnabled()) {
                LOG.debug("Property key '" + pPropertyName + "' set to '" + tFloatValue + "'");
            }
        } else {
            LOG.warn("Property key '" + pPropertyName + "' not found in config file. Using default value " + pDefaultValue);
        }
        return tFloatValue;
    }
	
	public boolean loadBooleanProperty(Properties pProperties, String pPropertyName, boolean pDefaultValue) {
	    boolean tBoolValue = pDefaultValue;
	    String tStringValue = pProperties.getProperty(pPropertyName);
	    if (tStringValue != null) {
            try {
            	tBoolValue = Boolean.parseBoolean(tStringValue);
    	    } catch (Exception e) {
    	        LOG.warn("Parsing of property '" + pPropertyName + "' from config file failed. Using default value.");
    	    }
    	    if (LOG.isDebugEnabled()) {
    	        LOG.debug("Property key '" + pPropertyName + "' set to '" + tBoolValue + "'");
    	    }
        } else {
            LOG.warn("Property key '" + pPropertyName + "' not found in config file. Using default value " + pDefaultValue);
        }
	    return tBoolValue;
	}

    public void findUnoccupiedPorts() {

        int tFirstNotOccupiedListeningPort = -1;
		for (int tCheckedPort = this.getListeningPort();
		     tCheckedPort <= this.getListeningPort() + 10;
		     tCheckedPort = tCheckedPort + 2) {
			try {
			    DatagramSocket ds = new DatagramSocket(tCheckedPort);
			    ds.close();
			    tFirstNotOccupiedListeningPort = tCheckedPort;
			    LOG.info("UDP Port " + tCheckedPort + " is open.");
			    break;
			}
			catch (IOException ex) {
			    LOG.warn("UDP Port " + tCheckedPort + " is occupied. Trying next ...");
			}
		}
		if (tFirstNotOccupiedListeningPort == -1) {
			String tErrorMessage = "No unoccupied UDP port between "
				+ this.getListeningPort() + " and " + (this.getListeningPort() + 10) + " found.";
			LOG.error(tErrorMessage);
			JOptionPane.showMessageDialog(null, tErrorMessage, "Network error", JOptionPane.ERROR_MESSAGE);
			System.exit(1);
		} else {
			this.setListeningPort(tFirstNotOccupiedListeningPort);
			this.setSendingPort(tFirstNotOccupiedListeningPort + 1);
			LOG.info("Set listening port to " + this.getListeningPort());
			LOG.info("Set sending port to " + this.getSendingPort());
		}

    }

    public File getDefaultArtworkFile() {
		return defaultArtworkFile;
	}

	public File getDefaultPlaylistDirectory() {
		return defaultPlaylistDirectory;
	}

    public boolean isUseTurntableForPlaylistSelection() {
        return useTurntableForPlaylistSelection;
    }

    public boolean isReselectPlaylistWhenTurntableSwitchedOff() {
        return reselectPlaylistWhenTurntableSwitchedOff;
    }

    public int getScreenWidth() {
		return screenWidth;
	}

	public int getScreenHeight() {
		return screenHeight;
	}

    public int getScreenRotation() {
        return screenRotation;
    }

    public boolean isFullScreenEnabled() {
		return fullScreenEnabled;
	}

	public int getItemCount() {
		return itemCount;
	}

	public float getVelocityFactor() {
		return velocityFactor;
	}

    public int getModeSwitchTimeInMs() {
        return modeSwitchTimeInMs;
    }

    public int getListeningPort() {
		return listeningPort;
	}

	public int getSendingPort() {
		return sendingPort;
	}

	public int getTextureCacheSize() {
		return textureCacheSize;
	}

	public int getMaximumFrameRate() {
		return maximumFrameRate;
	}

    public float getBrowsingScaleFactor() {
        return browsingScaleFactor;
    }

    public float getPlaybackScaleFactor() {
		return playbackScaleFactor;
	}

	public String getDebugFont() {
		return debugFont;
	}

	public String getStandardFont() {
		return standardFont;
	}

	public boolean isITunesParsingEnabled() {
		return iTunesParsingEnabled;
	}

	public File getITunesMusicLibrary() {
		return iTunesMusicLibraryFile;
	}

	public File getITunesToM3UStylesheetFile() {
		return iTunesToM3UStylesheetFile;
	}

    public float getPosCloudScaleFactor() {
        return posCloudScaleFactor;
    }

    public int getPosCloudXPos() {
        return posCloudXPos;
    }

    public int getPosCloudYPos() {
        return posCloudYPos;
    }

    public int getPosCloudRotation() {
        return posCloudRotation;
    }

    public void setListeningPort(int listeningPort) {
		this.listeningPort = listeningPort;
	}


	public void setSendingPort(int sendingPort) {
		this.sendingPort = sendingPort;
	}

    public void rotateScreen(int pDelta) {
        this.screenRotation = (this.screenRotation + pDelta) % 360;
        projProperties.setProperty("SCREEN_ROTATION", Integer.toString(screenRotation));
    }

    public void translatePosCloudXPos(int pDelta) {
        this.posCloudXPos = this.posCloudXPos + pDelta;
        projProperties.setProperty("POSCLOUD_XPOS", Integer.toString(posCloudXPos));
    }

    public void translatePosCloudYPos(int pDelta) {
        this.posCloudYPos = this.posCloudYPos + pDelta;
        projProperties.setProperty("POSCLOUD_YPOS", Integer.toString(posCloudYPos));
    }

    public void adjustPosCloudScaleFactor(float pDelta) {
        this.posCloudScaleFactor = this.posCloudScaleFactor + pDelta;
        projProperties.setProperty("POSCLOUD_SCALE_FACTOR", Float.toString(posCloudScaleFactor));
    }

    public void rotatePosCloud(int pDelta) {
        this.posCloudRotation = (this.posCloudRotation + pDelta) % 360;
        projProperties.setProperty("POSCLOUD_ROTATION", Integer.toString(posCloudRotation));
    }
}
